package ch.zbw.pra.sysventory.business.businessObject.scan;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zbw.pra.sysventory.dataaccess.dataAccessObject.ScanversionDataAccessObject;
import ch.zbw.pra.sysventory.model.Computer;
import ch.zbw.pra.sysventory.model.Scanversion;
import ch.zbw.pra.sysventory.utils.DateUtils;
import ch.zbw.pra.sysventory.utils.HostFinder;

/**
 * Steuer das ganze Scannen. Hier wird eine komplette Scanversion verwaltet
 * 
 * @author Ralph Koster
 */

public class ScanBusinessObject implements Callable<Scanversion>,
		Serializable {

	private static final long serialVersionUID = 5032128449404511074L;
	private Logger LOG = LoggerFactory.getLogger(ScanBusinessObject.class);
	
	private ScanversionDataAccessObject scanversionDataAccessObject;
	private Scanversion scanversion;
	private List<String> accessibleHosts;
	private List<Computer> computers;
	private String subnetMask;
	private ExecutorService executor;
	private boolean scanCanceled;
	
	public ScanBusinessObject() {
		this.computers = new ArrayList<Computer>();
		this.scanversionDataAccessObject = new ScanversionDataAccessObject();
	}
	
	@Override
	public Scanversion call() throws Exception {
		scanCanceled = false;
		prepareScan();
		createThreads();
		if(!scanCanceled) {
			finishScan();
			return scanversion;
		}
		return new Scanversion();
	}
	
	private void prepareScan() {
		String address = HostFinder.getLocalAddress();

		LOG.debug("==================================================================");
		LOG.debug("Start scanning...");

		scanversion = new Scanversion();
		scanversion.setNumberOfScanversion(new DateTime().getMillis());
		scanversion.setScanStartTimestamp(DateUtils.getCurrentSqlTimeStamp());

		accessibleHosts = HostFinder.getAListOfAccessibleHosts(address, subnetMask);
	}
	
	private void createThreads() {
		executor = Executors.newCachedThreadPool();
		List<Callable<Computer>> tasks = new ArrayList<Callable<Computer>>();
		for (String host : accessibleHosts) {
			tasks.add(new ScanComputerBusinessObject(host));
		}
	    try {
	    	List<Future<Computer>> futureComputers = executor.invokeAll(tasks);
	    	for(Future<Computer> computer : futureComputers) {
	    		if(null != computer && computer.isDone()) {
	    			try {
	    				Computer tempComputer = computer.get();
	    				if(null != tempComputer) {
	    					computers.add(tempComputer);
	    				}
					} catch (ExecutionException e) {
						LOG.debug("Could not add the future object of computer to computers list");
						LOG.error(e.toString());
					}
	    		}
	    	}
		} catch (InterruptedException e) {
			LOG.debug("Failed to load computer from future");
			LOG.error(e.toString());
			return;
		}
	}
	
	private void finishScan() {
		scanversion.setComputers(computers);
		scanversion.setScanEndeTimestamp(DateUtils.getCurrentSqlTimeStamp());
		scanversionDataAccessObject.saveScanversion(scanversion);
		LOG.debug("Scan finished...");
		LOG.debug("===============================================================\n");
	}
	
	public Scanversion getScanversion() {
		return this.scanversion;
	}
	
	public void addComputerToList(Computer computer) {
		computers.add(computer);
	}

	public void setSubnetMask(String subnetMask) {
		this.subnetMask = subnetMask;		
	}

	public void interruptAllThreads() {
		executor.shutdownNow();
		scanCanceled = true;
		LOG.debug("Scan cancelled by user...");
		LOG.debug("===============================================================\n");
	}
}
